﻿using System;
using System.Collections.Concurrent;
using System.ComponentModel.Composition;
using System.Linq;
using System.ServiceModel;
using Cubicle.Core.DataContracts;
using Cubicle.Core.Hosting;
using Cubicle.Core.Interfaces;
using Cubicle.Core.Shared;

namespace Cubicle.Core.Repository
{

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    [ServiceContract(SessionMode = SessionMode.Allowed)]
    [UseNetDataContractSerializer]
    [Export("ExcelRepository", typeof(IRepository))]
    [ExportMetadata("Name", "ExcelRepository")]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class Repository : IRepository
    {
        // The string that will be prefixed to every name to generate a key: e.g. host:domain
        public const string ThisHandle = "!this";

        // Map : key -> object
        protected readonly ConcurrentDictionary<string, IRObject> Store = new ConcurrentDictionary<string, IRObject>();

        // Handle generator
        [Import] 
        private IHandleRegister _handleRegistry;

        // Events
        public event EventHandler<RepoEventArgs> SetEvent;
        public event EventHandler<RepoEventArgs> GetEvent;
        public event EventHandler<RepoEventArgs> RemoveEvent;

        private void DisposeRemove(object sender, RepoEventArgs e)
        {
            var obj = e.TargetRObject.Object;
            if (e.TargetRObject.Object is IDisposable)
                ((IDisposable) obj).Dispose();
        }

        [Help(Description = "Return an RObject from the repository. Retrieving an object required read permission.")]
        public virtual IRObject GetRObject(string handle)
        {
            // Verify that the object exists
            if (handle == null) return null;

            // Verify that the object exists
            IRObject result;
            Store.TryGetValue(handle, out result);
            if (result == null) return null;

            // Fire Get events
            var args = new RepoEventArgs(result);
            if (GetEvent != null) GetEvent(this, args);
            result.Parser.OnGet(this, args);
            result.OnGet(this, args);

            return result;
        }

        [Help(Description = "Unwrap the RObject corresponding to a given key and return it's contents.")]
        public object Get(string handle)
        {
            // Check for special case
            if (handle == ThisHandle) return this;

            // Retrieve the RObject
            var rObj = GetRObject(handle);

            // Return the object
            return rObj == null ? null : rObj.Object;
        }

        [Help(Description = "Unwrap the RObject and cast to specific type.")]
        public T Get<T>(string handle)
        {
            // If the key does not exist this will merely typecast the null, 
            // which shouldn't cause any issues.
            return (T)Get(handle);
        }

        [Help(Description = "Add an object to the repository. This will replace an existing object under \"name\" only if its permissions allow writing")]
        public virtual string Set(string key, object item)
        {
            return Set(MakeRObject(key, item));
        }

        protected virtual IRObject MakeRObject(string key, object item)
        {
            var parser = _handleRegistry.KeyToParser(key);
            return new RObject(parser, key, item, this);
        }

        [Help(Description = "Add an object to the repository. This will replace an existing object under \"name\" only if its permissions allow writing")]
        public string Set(IRObject rObject)
        {
            // If the name already exists, try to remove the current reference
            var handle = rObject.Handle;

            // Wrap the object in an RObject and save the (key, RObject) pair in the _store dictionary
            Store.AddOrUpdate(handle, rObject, (key, value) => rObject);

            // Fire Set event
            var args = new RepoEventArgs(rObject);
            if (SetEvent != null) SetEvent(this, args);
            rObject.Parser.OnSet(this, args);
            rObject.OnSet(this, args);

            // Return handle
            return handle;
        }

        public bool Has(string handle)
        {
            return handle != null && (handle == ThisHandle || Store.ContainsKey(handle));
        }

        [Help(Description = "Removed an object from the repository.")]
        public virtual IRObject Remove(string handle)
        {
            var obj = Remove(handle, false);
            return obj;
        }

        [Help(Description = "Removed an object from the repository and the object's dispose method.")]
        public virtual bool DisposeRemove(string handle)
        {
            var obj = Remove(handle, true);
            return obj != null;
        }

        private IRObject Remove(string handle, bool dispose)
        {
            // Check if handle is valid
            if (handle == null) return null;

            IRObject result;
            Store.TryRemove(handle, out result);
            if (result == null) return result;

            // Create event args
            var args = new RepoEventArgs(result);

            // Remove event
            if (RemoveEvent != null) RemoveEvent(this, args);

            // CortexAware remove event
            result.OnRemove(this, args);

            // Dispose if required
            if (dispose && result.Object is IDisposable)
                ((IDisposable) result.Object).Dispose();

            return result;
        }

        [Help(Description = "Change the name of an existing object. If an object is already associated with \"newName\" the rename will fail.")]
        public virtual string Rename(
            [Help(Description = "The object handle")] string oldHandle,
            [Help(Description = "The new name of the object")] string newKey)
        {
            var payload = GetRObject(oldHandle);
            if (payload == null) return null;
            var newHandle = _handleRegistry.KeyToHandle(newKey);
            if (Has(newHandle)) return null;
            Remove(oldHandle);
            payload.Key = newKey;
            payload.Handle = newHandle;
            Set(payload);
            return newHandle;
        }

        public string[] List()
        {
            return Store.Keys.ToArray();
        }

        public void Clear()
        {
            Store.Clear();
        }

    }
}